<?php
	abstract class FilterNode implements iExtensionsRequired {
		protected $Line = 0;
		protected $Children = array ();
		public static function CreateNode ($Line, $String) {
			@list ($Type, $String) = explode (' ', $String, 2);
			if ('if' === $Type) {
				@list ($Field, $OperatorRaw, $Operand) = explode (' ', $String, 3);
				switch ($OperatorRaw) {
					case '=':            $Operator = 'eq';  break;
					case '==':           $Operator = 'eq';  break;
					case 'is':           $Operator = 'eqi'; break;
					case '>':            $Operator = 'gt'; break;
					case '>=':           $Operator = 'ge'; break;
					case '<':            $Operator = 'lt'; break;
					case '<=':           $Operator = 'le'; break;
					case 'begins_with':  $Operator = 'bw';  break;
					case 'ibegins_with': $Operator = 'bwi'; break;
					case 'ends_with':    $Operator = 'ew';  break;
					case 'iends_with':   $Operator = 'ewi'; break;
					case 'contains':     $Operator = 'co';  break;
					case 'icontains':    $Operator = 'coi'; break;
					case 'matches':      $Operator = 'ma';  break;
					default:
						Out::W (Msg::FilterUnknownOp, $Line, $OperatorRaw);
						return (NULL);
				}
				$First = substr ($Operand, 0, 1);
				$Last = substr ($Operand, -1);
				((('"' == $First) and ('"' == $Last)) or (("'" == $First) and ("'" == $Last))) and $Operand = substr ($Operand, 1, -1);
				return (new FilterNodeCondition ($Line, $Field, $Operator, $Operand));
			} elseif (class_exists ($Type) and HasInterface ($Type, 'iFilter')) {
				return (new FilterNodeAction ($Line, $Type, $String));
			}
			Out::W (Msg::FilterUnknownAct, $Line, $Type);
			return (NULL);
		}
		public function AddChild (FilterNode $Node) {
			array_push ($this->Children, $Node);
		}
		public function AddString ($Line, $String) {
			$Child = self::CreateNode ($Line, $String);
			is_subclass_of ($Child, __CLASS__) and $this->AddChild ($Child);
			return ($Child);
		}
		public function Render ($Prefix = '') {
			$Class = get_class ($this);
			$Props = '';
			foreach ($this as $K => $V) {
				if (! is_scalar ($V)) $V = gettype ($V).'('.count ($V).')';
				$Props .= "$K:$V ";
			}
			$Result = "{$Prefix}{$Class} $Props\n";
			foreach ($this->Children as $Child) $Result .= $Child->Render ("$Prefix  ");
			return ($Result);
		}
		public function Examine (array $Line, array $Chain = array ()) {
			// first determine the value of this node
			switch (get_class ($this)) {
				case 'FilterNodeRoot': break; // nothing to do here
				case 'FilterNodeAction': array_push ($Chain, $this->Class); break; // always add our action
				case 'FilterNodeCondition':
					$Match = FALSE;
					if (array_key_exists ($this->Field, $Line)) {
						list ($F, $O) = array ($Line [$this->Field], $this->Operand);
						switch ($this->Operator) {
							case 'eqi': $Match = (0 === strcasecmp ($F, $O)); break;
							case 'eq':  $Match = ($F == $O); break;
							case 'gt':  $Match = ($F >  $O); break;
							case 'ge':  $Match = ($F >= $O); break;
							case 'lt':  $Match = ($F <  $O); break;
							case 'le':  $Match = ($F <= $O); break;
							case 'bw':  $Match = (0 === strpos  ($F, $O)); break;
							case 'bwi': $Match = (0 === stripos ($F, $O)); break;
							case 'ew':  $Match = (strlen ($F) - strlen ($O) === strrpos  ($F, $O)); break;
							case 'ewi': $Match = (strlen ($F) - strlen ($O) === strripos ($F, $O)); break;
							case 'co':  $Match = (FALSE !== strpos  ($F, $O)); break;
							case 'coi': $Match = (FALSE !== stripos ($F, $O)); break;
							case 'ma':  $Match = (preg_match ($O, $F)); break;
							case 'ERR': $Match = FALSE;
						}
					} else {
						Out::W (Msg::FilterUnknownCol, $this->Line, $this->Field);
						return ('kill me');
					}
					if (! $Match) return (array ()); // bail out if this node's condition doesn't match
				break;
			}
			// then descend to children if there are any
			$TestChain = $Chain;
			foreach ($this->Children as $CID => $Child) {
				$TestChain = call_user_func (array ($Child, __FUNCTION__), $Line, $Chain);
				if ('kill me' === $TestChain) {
					unset ($this->Children [$CID]);
				} elseif (count ($TestChain)) {
					$Chain = $TestChain;
					break;
				}
			}
			return (count ($TestChain)) ? $Chain : array ();
		}
		public static function __GetRequiredExtensions () {return ('pcre');}
	}
	class FilterNodeRoot extends FilterNode {
		public function FilterNodeRoot () {}
	}
	class FilterNodeAction extends FilterNode {
		protected $Class = '';
		protected $Arguments = '';
		public function FilterNodeAction ($Line, $Class, $Arguments) {
			$this->Line = $Line;
			$this->Class = $Class;
			$this->Arguments = $Arguments;
		}
	}
	class FilterNodeCondition extends FilterNode {
		protected $Field = '';
		protected $Operator = '';
		protected $Operand = '';
		public function FilterNodeCondition ($Line, $Field, $Operator, $Operand) {
			$this->Line = $Line;
			$this->Field = $Field;
			$this->Operator = $Operator;
			$this->Operand = $Operand;
		}
	}
?>